<?php
/**
 * Description: 商品整妆服务类
 * Author: hezhizheng
 * Date: 2018-9-20 17:00:13
 * Created by Created by Panxsoft.
 */

namespace App\Services\Repositories\Pack;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use App\Models\Customer\Asset;
use App\Models\Customer\Customer;
use App\Models\Customer\PackAssetPivotProduct;
use App\Models\Panx\Category;
use App\Models\Panx\Product;
use App\Models\Store\Pack;
use App\Models\Store\PackCategory;
use App\Services\Product\CopyProductService;
use App\Services\Utils\ApiChecker;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\DB;

class CopyPackService
{
    use ApiChecker;

    /**
     * 将指定整妆复制给指定 customer
     *
     * @test
     * @param Pack $old_pack
     * @param Customer $customer
     *
     * @return Pack
     */
    public function toCustomer(Pack $old_pack, Customer $customer)
    {
        $changes = $this->copyChangeProperties($old_pack, $customer);
        $sames = $this->copyPackProperties($old_pack);
        $belongs = $this->copyPackBelongs($old_pack, $customer);

        /** @var Pack $new_pack */
        $new_pack = (new Pack)->create($changes + $sames + $belongs);

        $this->copyPackRelationPivots($old_pack, $new_pack, $customer);

        $old_pack->incrementRefCount();

        return $new_pack;
    }

    /**
     * 提取产品自身属性（无关联）
     *
     * @param Pack $old_pack
     *
     * @return array
     */
    private function copyPackProperties(Pack $old_pack): array
    {
        return [
            // props
            'name'       => $old_pack->name,
            'thumb'      => $old_pack->thumb,
            'priority'   => $old_pack->priority,
            'is_deleted' => $old_pack->is_deleted,
        ];
    }

    /**
     * BelongsTo
     *
     * @param Pack $old_pack
     * @param Customer $customer
     *
     * @return array
     */
    private function copyPackBelongs(Pack $old_pack, Customer $customer): array
    {
        $customer_id = $customer->getKey();

        $pack_category_id = $old_pack->packCategory
            ? $this->copyPackCategory($old_pack->packCategory, $customer_id)->id
            : 0;

        return [
            // belongs to relations
            'customer_id'      => $customer_id,
            'store_id'         => Product::STORE_ID_CUSTOMER,
            'pack_category_id' => $pack_category_id,

            // new belongs id
            'src_pack_id'      => $old_pack->id,
        ];
    }


    /**
     * @param PackCategory $old_pack_cate
     * @param int $customer_id
     *
     * @return packCategory
     */
    private function copyPackCategory(PackCategory $old_pack_cate,
                                      int $customer_id): PackCategory
    {
        /** @var PackCategory $new_pack_cate */
        $new_pack_cate = (new PackCategory)->firstOrCreate([
            'customer_id' => $customer_id,
            'name'        => $old_pack_cate->name,
        ], [
            'priority' => $old_pack_cate->priority,
            'image'    => $old_pack_cate->image,
        ]);

        return $new_pack_cate;
    }


    /**
     * 复制整妆关联表
     *
     * @param Pack $old
     * @param Pack $new
     * @param Customer $customer
     */
    private function copyPackRelationPivots(Pack $old, Pack $new, Customer $customer): void
    {
        $customer_id = $customer->id;

        $new_assets = $this->copyPackAssets($old, $customer_id);

        $this->copyPackPivotAsset($old, $new, $new_assets);

        $this->copyPackAssetPivotProduct($old, $new, $customer, $new_assets);
    }

    /**
     * 复制需要动态变化的属性
     *
     * @param Pack $old_pack
     * @param Customer $customer
     *
     * @return array
     */
    private function copyChangeProperties(Pack $old_pack, Customer $customer): array
    {
        $copied_counts = (new Pack)
            ->where('customer_id', $customer->id)
            ->where('src_pack_id', $old_pack->id)
            ->count();
        $new_spu_number = $copied_counts + 1;

        return [
            'name' => "{$old_pack->name}($new_spu_number)",
        ];
    }


    /**
     * @param Collection $packs
     * @param Customer $customer
     * @return Collection
     * @throws \Exception
     */
    public function batchToCustomer(Collection $packs, Customer $customer): Collection
    {
        $product_ids = (new PackAssetPivotProduct)
            ->whereIn('pack_id', $packs->pluck('id'))
            ->pluck('product_id');

        $products = (new Product)->find($product_ids);

        $this->checkCustomerHasProductsCategories($products, $customer);

        return $packs->map(function (Pack $pack) use ($customer) {
            return $this->toCustomer($pack, $customer);
        });
    }


    /**
     * @param array $need_category_ids
     * @param Customer $customer
     * @throws \Exception
     */
    public function checkCustomerHasCategories(array $need_category_ids, Customer $customer): void
    {
        $category = new Category;

        $has_ids = $customer
            ->categories
            ->pluck('id')
            ->all();

        $lack_ids = array_values(array_diff($need_category_ids, $has_ids));

        if (count($lack_ids)) {
            $lack_names = $category
                ->whereKey($lack_ids)
                ->pluck('name')
                ->implode('，');

            throw new ApiException(ApiErrCode::OPERATE_FAILED, '客户缺少以下分类：' . $lack_names);
        }
    }


    /**
     * @param Collection $products
     * @param Customer $customer
     * @throws \Exception
     */
    private function checkCustomerHasProductsCategories(Collection $products, Customer $customer): void
    {
        $category_ids = $products->pluck('category_id')->unique()->all();

        $this->checkCustomerHasCategories($category_ids, $customer);
    }

    /**
     * @deprecated - ??
     *
     * @param Collection $products
     * @param Customer $customer - to customer
     *
     * @return Collection|Product[]
     */
    private function copyProducts(Collection $products, Customer $customer): Collection
    {
        $customer_products = $customer->products()->get();

        $new_products = $products->map(function (Product $product) use ($customer, $customer_products) {

            $copied_counts = $customer_products->where('src_product_id', $product->id)->count();

            $find = (new Product)
                ->where([
                    ['spu_code', $product->spu_code],
                    ['customer_id', $customer->id],
                ])
                ->when($copied_counts, function (Builder $query) use ($product, $copied_counts, $customer) {
                    $query->orWhere([
                        ['spu_code',  $product->spu_code . "($copied_counts)"], // spu_code(n)
                        ['customer_id', $customer->id],
                    ]);
                })
                ->first();

            if ($find) {
                return $find;
            } else {
                return (new CopyProductService)->toCustomer($product, $customer);
            }

        });
        return $new_products;
    }

    /**
     * @param Pack $old
     * @param Pack $new
     * @param $new_assets
     * @return array
     */
    private function assembleNewPackPivotAssetData(Pack $old, Pack $new, Collection $new_assets): array
    {
        $old_code_index_map = $old->assets->pluck('pivot.index', 'code');
        $new_code_asset_id_map = $new_assets->pluck('id', 'code')->toArray();

        $new_pack_pivot_asset = [];

        $now = now();

        foreach ($old_code_index_map as $code => $index) {
            $new_pack_pivot_asset[] = [
                'pack_id'    => $new->id,
                'asset_id'   => $new_code_asset_id_map[$code],
                'index'      => $index,
                'created_at' => $now,
                'updated_at' => $now,
            ];
        }
        return $new_pack_pivot_asset;
    }

    /**
     * 复制整妆对应单妆素材
     *
     * @param Pack $old
     * @param $customer_id
     * @return Collection|Asset[]
     */
    private function copyPackAssets(Pack $old, int $customer_id): Collection
    {
        $new_assets = $old->assets->map(function (Asset $asset) use ($customer_id) {
            return (new Asset)->firstOrCreate([
                'customer_id' => $customer_id,
                'code'        => $asset->code,
            ], [
                'model_xml'   => $asset->model_xml,
                'version'     => $asset->version,
                'category_id' => $asset->category_id,
            ]);
        });

        return $new_assets;
    }

    /**
     * 复制整妆与单妆素材对应关联表
     *
     * @param Pack $old
     * @param Pack $new
     * @param Collection $new_assets
     */
    private function copyPackPivotAsset(Pack $old, Pack $new, Collection $new_assets): void
    {
        $new_pack_pivot_asset_data = $this->assembleNewPackPivotAssetData($old, $new, $new_assets);

        // 写入 pack_pivot_asset 表数据
        DB::table('pack_pivot_asset')->insert($new_pack_pivot_asset_data);
    }

    /**
     * 复制整妆与商品的对应关联表
     *
     * @param Pack $old
     * @param Pack $new
     * @param Customer $customer
     * @param Collection|Asset[] $new_assets
     */
    private function copyPackAssetPivotProduct(Pack $old, Pack $new,
                                               Customer $customer,
                                               Collection $new_assets): void
    {
        /** @var Collection|PackAssetPivotProduct[] $old_pivots */
        $old_pivots = $old->assetProductPivots()
            ->with(['product', 'asset'])
            ->get();

        $new_assets_code_id_map = $new_assets
            ->pluck('id', 'code')
            ->all();

        $now = now();

        $new_pivots_data = $old_pivots
            ->map(function (PackAssetPivotProduct $pivot) use ($new, $customer, $new_assets_code_id_map, $now) {
                $new_product = $this->findBySpuCodeOrCopyProduct($pivot->product, $customer);

                return [
                    'pack_id'    => $new->id,
                    'asset_id'   => $new_assets_code_id_map[$pivot->asset->code],
                    'product_id' => $new_product->id,
                    'created_at' => $now,
                    'updated_at' => $now,
                ];
            })
            ->all();

        // 写入 pack_asset_pivot_product 表数据
        DB::table('pack_asset_pivot_product')->insert($new_pivots_data);
    }

    /**
     * @param Product $old
     * @param Customer $customer
     *
     * @return Product
     * @throws \Exception
     */
    private function findBySpuCodeOrCopyProduct(Product $old, Customer $customer): Product
    {
        $this->loadCustomerSelfProducts($customer);

        /** @var Product|null $customer_product */
        $customer_product = $customer
            ->selfProducts
            ->first(function (Product $product) use ($old) {
                // pattern like "spu-code" or "spu-code(123)"
                // 实际 $old->spu_code 不会出现 $old->spu_code(n)的情况
                return (bool) preg_match("/^$old->spu_code(\(\d+\))?$/", $product->spu_code);
            });

        return $customer_product ?? (new CopyProductService)->toCustomer($old, $customer);
    }

    /**
     * @param Customer $customer
     */
    private function loadCustomerSelfProducts(Customer $customer): void
    {
        if (! $customer->relationLoaded('selfProducts')) {
            $customer->load(['selfProducts' => function (HasMany $query) {
                $query->select(['id', 'spu_code']);
            }]);
        }
    }
}